/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// @ts-nocheck
declare function print(arg: any, arg1?: any): string;
// function print(arg: any, arg1?: any): string {
//     console.log(arg)
// }

declare interface ArkTools{
	timeInUs(arg:any):number
}
var suites: Array<{
    name: string,
    run: Function
}> = [];
var scores: Array<string> = [];

class NodeValue { 
    value?: { array: number[], string: string };
    left?: NodeValue; 
    right?: NodeValue;

    constructor(value?: { array: number[], string: string }, left?: NodeValue, right?: NodeValue) {
        this.value = value;
        this.left = left;
        this.right = right;
    }
}

class MyNode {
    key: number;
    value: NodeValue;
    left?: MyNode;
    right?: MyNode;

    constructor(key: number, value?: NodeValue) {
        this.key = key;
        if (value) {
            this.value = value;
        }
    }

    traverse_(f: Function) {
        var current: MyNode = this;
        while (current) {
            var left: MyNode = current.left;
            if (left) left.traverse_(f);
            f(current);
            current = current.right;
        }
    }
}

class BenchmarkSuite {
    name: string;
    run: Function;

    constructor(name: string, run: Function) {
        this.name = name;
        this.run = run;
        suites.push(this);
    }

    static countBenchmarks(): number {
        var count: number = suites.length;
        return count;
    }

    static sumScores(): string {
        var num: number = 0;
        var len: number = scores.length;
        for (var j = 0; j < len; j++) {
            num += parseInt(scores[j]);
        }
        return num.toString();
    }

    static runSuites(runner, singleItem): void {
        scores = [];
        var length: number = suites.length;
        for (var i = 0; i < length; i++) {
            if (singleItem) {
                if (suites[i].name == singleItem) {
                    this.runSingleBenchmark(i, suites[i].name, runner);
                    break;
                } else {
                    continue;
                }
            } else {
                this.runSingleBenchmark(i, suites[i].name, runner);
            }
        }
        /*
           计算总分的逻辑
        */
        var scoreTotal: string = this.sumScores();
        runner.NotifyScore(scoreTotal);
    }

    static runSingleBenchmark(index, name, runner): void {
        /*
              单项跑分的逻辑
           */
        try {
            var result: string = suites[index].run();
            runner.NotifyResult(name, result);
            runner.NotifyStep();
            scores.push(result);
        } catch (error) {
            runner.NotifyError(name, error)
        }
    }


    static ResetRNG(): void {
        Math.random = (function () {
            var seed = 49734321;
            return function () {
                // Robert Jenkins' 32 bit integer hash function.
                seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff;
                seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
                seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff;
                seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff;
                seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff;
                seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
                return (seed & 0xfffffff) / 0x10000000;
            };
        })();
    }

    static FormatScore(value: number): string {
        return value.toFixed(0);
    }
}

class Benchmark {
    name: string;
    doWarmup: boolean;
    doDeterministic: boolean;
    run: Function;
    Setup: Function;
    TearDown: Function;
    rmsResult: Function;
    minIterations: number;
    data: {
        runs: number,
        elapsed: number
    };

    constructor(name: string, doWarmup: boolean, doDeterministic: boolean, run: Function,
        setup?: Function, tearDown?: Function, rmsResult?: Function, minIterations?: number) {
        this.name = name;
        this.doWarmup = doWarmup;
        this.doDeterministic = doDeterministic;
        this.run = run;
        this.Setup = setup ? setup : function () {
        };
        this.TearDown = tearDown ? tearDown : function () {
        };
        this.rmsResult = rmsResult ? rmsResult : null;
        this.minIterations = 32;
        this.data = null;
    }

    RunNextSetup(benchmarkResult: BenchmarkResult): void {
        try {
            this.Setup();
        } catch (e) {
        }
        this.RunNextBenchmark(benchmarkResult);
    }

    RunNextBenchmark(benchmarkResult: BenchmarkResult): void {
        try {
            this.RunSingle(benchmarkResult);
        } catch (e) {

        }
        (this.data == null) ? this.RunNextTearDown() : this.RunNextBenchmark(benchmarkResult);
    }

    RunNextTearDown(): void {
        try {
            this.TearDown();
        } catch (e) {
        }
    }

    RunSingle(benchmarkResult: BenchmarkResult): void {
        // Sets up data in order to skip or not the warmup phase.
        if (!this.doWarmup && this.data == null) {
            this.data = { runs: 0, elapsed: 0 };
        }

        if (this.data == null) {
            this.Measure();
            this.data = { runs: 0, elapsed: 0 };
        } else {
            this.Measure();
            // If we've run too few iterations, we continue for another second.
            if (this.data.runs < this.minIterations) return;
            var usec: number = (this.data.elapsed * 1000) / this.data.runs;
            var rms: number = (this.rmsResult != null) ? this.rmsResult() : 0;
            benchmarkResult.time = usec;
            benchmarkResult.latency = rms;
            this.data = null;
        }
    }

    Measure(): void {
        var elapsed: number = 0;
        var start: number = ArkTools.timeInUs();

        // Run either for 1 second or for the number of iterations specified
        // by minIterations, depending on the config flag doDeterministic.
        for (var i = 0; (this.doDeterministic ?
            i < this.minIterations : elapsed < 1000); i++) {
            this.run();
            elapsed = (ArkTools.timeInUs() - start) / 1000;
        }
        if (this.data != null) {
            this.data.runs += i;
            this.data.elapsed += elapsed;
        }
    }
}

class BenchmarkResult {
    benchmark: string;
    time: number;
    latency: number;

    constructor(benchmark: string, time: number, latency: number) {
        this.benchmark = benchmark;
        this.time = time;
        this.latency = latency;
    }
}

new BenchmarkSuite('Splay', runSplay);

function runSplay() {
    var benchmark = new Benchmark('Splay', true, false, SplayRun, SplaySetup, SplayTearDown, SplayRMS);
    var benchmarkResult = new BenchmarkResult('Splay', 0, 0);
    BenchmarkSuite.ResetRNG();
    benchmark.RunNextSetup(benchmarkResult);

    var score: number = benchmarkResult.time;
    var formatted: string = BenchmarkSuite.FormatScore(score);
    if (reference.length == 2 && benchmarkResult.latency != 0) {
        var scoreLatency: number = benchmarkResult.latency / reference[1];
        var formattedLatency: string = BenchmarkSuite.FormatScore(100 * scoreLatency);
        var finalScore: string = benchmark.name + 'Latency: ' + formattedLatency;
        print(finalScore);
    }

    return formatted;
}

// Configuration.
var splayTreeSize: number = 8000;
var splayTreeModifications: number = 80;
var splayTreePayloadDepth: number = 5;
var splayTree: SplayTree = null;
var splaySampleTimeStart: number = 0.0;
var splaySamples: number = 0;
var splaySumOfSquaredPauses: number = 0;
//reference in set for calculate Splay score
var reference: Array<number> = [81491, 273];

function GeneratePayloadTree(depth: number, tag: string): NodeValue {
    if (depth == 0) {
        return new NodeValue({
            array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
            string: 'String for key ' + tag + ' in leaf node'
        }, undefined, undefined);
    } 
    
    let curDep = depth;

    let inArray: Array<NodeValue> = new Array(2 ** (depth - 1));
    let outArray: Array<NodeValue>  = new Array(2 ** (depth - 1));

    let left: NodeValue = null;
    let right: NodeValue = null;

    inArray[0] = new NodeValue(undefined, left, right); 

    while (curDep--) {
        let nodeNum: number = 1;
        if (depth - curDep - 1 > 0) {
            nodeNum = 2 << (depth - curDep - 2);
        }
        for (let i = 0; i < nodeNum; i++) {
            let node = inArray[i];
            if (curDep > 0) {
                let left1: NodeValue = null;
                let right1: NodeValue = null;
                let left2: NodeValue = null;
                let right2: NodeValue = null;
                node.left = new NodeValue(undefined, left1, right1); 
                node.right = new NodeValue(undefined, left2, right2); 
                outArray[2 * i] = node.left;
                outArray[2 * i + 1] = node.right;
            } else {
                let resultArray: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
                let resultString: string = 'String for key ' + tag + ' in leaf node';
                node.left = new NodeValue({
                    resultArray,
                    resultString
                }, undefined, undefined); 
                node.right = new NodeValue({
                    resultArray,
                    resultString
                }, undefined, undefined); 
            }
        }

        let tmpArray = inArray;
        inArray = outArray;
        outArray = tmpArray;
    }

    return new NodeValue(undefined, left, right); 
}


function GenerateKey(): number {
    // The benchmark framework guarantees that Math.random is deterministic
    return Math.random();
}

function SplayRMS(): number {
    return Math.round(Math.sqrt(splaySumOfSquaredPauses / splaySamples) * 10000);
}

function SplayUpdateStats(time: number): void {
    var pause = (time - splaySampleTimeStart) / 1000;
    splaySampleTimeStart = time;
    splaySamples++;
    splaySumOfSquaredPauses += pause * pause;
}

function InsertNewNode(): number {
    // Insert new node with a unique key.
    var key: number;
    do {
        key = GenerateKey();
    } while (splayTree.find(key) != null);
    var payload = GeneratePayloadTree(splayTreePayloadDepth, String(key));
    splayTree.insert(key, payload);
    return key;
}


function SplaySetup() {
    splayTree = new SplayTree();
    splaySampleTimeStart = ArkTools.timeInUs()
    for (var i = 0; i < splayTreeSize; i++) {
        InsertNewNode();
        if ((i + 1) % 20 == 19) {
            SplayUpdateStats(ArkTools.timeInUs());
        }
    }
}


function SplayTearDown() {
    var keys: Array<number> = splayTree.exportKeys();
    splayTree = null;

    splaySamples = 0;
    splaySumOfSquaredPauses = 0;

    // Verify that the splay tree has the right size.
    var length: number = keys.length;
    if (length != splayTreeSize) {
        throw new Error("Splay tree has wrong size");
    }

    // Verify that the splay tree has sorted, unique keys.
    for (var i = 0; i < length - 1; i++) {
        if (keys[i] >= keys[i + 1]) {
            throw new Error("Splay tree not sorted");
        }
    }
}


function SplayRun() {
    // Replace a few nodes in the splay tree.
    for (var i = 0; i < splayTreeModifications; i++) {
        var key: number = InsertNewNode();
        var greatest: MyNode = splayTree.findGreatestLessThan(key);
        if (greatest == null) splayTree.remove(key);
        else splayTree.remove(greatest.key);
    }
    SplayUpdateStats(ArkTools.timeInUs());
}


class SplayTree {
    root_: MyNode;

    constructor() {
        this.root_ = null;
    }

    isEmpty(): boolean {
        return !this.root_;
    }

    insert(key: number, value: NodeValue): void {
        if (this.isEmpty()) {
            this.root_ = new MyNode(key, value);
            return;
        }
        // Splay on the key to move the last node on the search path for
        // the key to the root of the tree.
        this.splay_(key);
        if (this.root_.key == key) {
            return;
        }
        var node: MyNode = new MyNode(key, value);
        if (key > this.root_.key) {
            node.left = this.root_;
            node.right = this.root_.right;
            this.root_.right = null;
        } else {
            node.right = this.root_;
            node.left = this.root_.left;
            this.root_.left = null;
        }
        this.root_ = node;
    }

    remove(key: number): MyNode {
        if (this.isEmpty()) {
            throw Error('Key not found: ' + key);
        }
        this.splay_(key);
        if (this.root_.key != key) {
            throw Error('Key not found: ' + key);
        }
        var removed: MyNode = this.root_;
        if (!this.root_.left) {
            this.root_ = this.root_.right;
        } else {
            var right: MyNode = this.root_.right;
            this.root_ = this.root_.left;
            // Splay to make sure that the new root has an empty right child.
            this.splay_(key);
            // Insert the original right child as the right child of the new
            // root.
            this.root_.right = right;
        }
        return removed;
    }

    find(key: number): MyNode {
        if (this.isEmpty()) {
            return null;
        }
        this.splay_(key);
        return this.root_.key == key ? this.root_ : null;
    }

    findMax(opt_startNode: MyNode): MyNode {
        if (this.isEmpty()) {
            return null;
        }
        var current: MyNode = opt_startNode || this.root_;
        while (current.right) {
            current = current.right;
        }
        return current;
    }

    findGreatestLessThan(key: number): MyNode {
        if (this.isEmpty()) {
            return null;
        }
        // Splay on the key to move the node with the given key or the last
        // node on the search path to the top of the tree.
        this.splay_(key);
        // Now the result is either the root node or the greatest node in
        // the left subtree.
        if (this.root_.key < key) {
            return this.root_;
        } else if (this.root_.left) {
            return this.findMax(this.root_.left);
        } else {
            return null;
        }
    }

    exportKeys(): Array<number> {
        var result: Array<number> = [];
        if (!this.isEmpty()) {
            this.root_.traverse_(function (node: MyNode) {
                result.push(node.key);
            });
        }
        return result;
    }

    splay_(key: number): void {
        if (this.isEmpty()) {
            return;
        }
        // Create a dummy node.  The use of the dummy node is a bit
        // counter-intuitive: The right child of the dummy node will hold
        // the L tree of the algorithm.  The left child of the dummy node
         // will hold the R tree of the algorithm.  Using a dummy node, left
        // and right will always be nodes and we avoid special cases.
        var dummy: MyNode;
        var left: MyNode;
        var right: MyNode;
        dummy = left = right = new MyNode(0);
        var current: MyNode = this.root_;
        while (true) {
            if (key < current.key) {
                if (!current.left) {
                    break;
                }
                if (key < current.left.key) {
                    // Rotate right.
                    var tmp: MyNode = current.left;
                    current.left = tmp.right;
                    tmp.right = current;
                    current = tmp;
                    if (!current.left) {
                        break;
                    }
                }
                // Link right.
                right.left = current;
                right = current;
                current = current.left;
            } else if (key > current.key) {
                if (!current.right) {
                    break;
                }
                if (key > current.right.key) {
                    // Rotate left.
                    var tmp: MyNode = current.right;
                    current.right = tmp.left;
                    tmp.left = current;
                    current = tmp;
                    if (!current.right) {
                        break;
                    }
                }
                // Link left.
                left.right = current;
                left = current;
                current = current.right;
            } else {
                break;
            }
        }
        // Assemble.
        left.right = current.left;
        right.left = current.right;
        current.left = dummy.right;
        current.right = dummy.left;
        this.root_ = current;
    }
}


function showProgress(name) {
}

function addError(name, error) {
    print(" " + name + ":" + error);
}

function addScore(score) {
}

function addResult(name, result) {
    print(name + ":\t" + result + "\tms");
}

BenchmarkSuite.runSuites({
    NotifyStep: showProgress,
    NotifyError: addError,
    NotifyResult: addResult,
    NotifyScore: addScore
}, "");
